package edu.rit.drh4818.raytracing.util;

import edu.rit.drh4818.raytracing.object.SceneObject;

/**
 * A ray is a path of light, traveling along a certain path.
 *  A ray is mapped using its origin and a direction defined by 
 *  slope in 3 dimensions.
 * 
 * @author Darren Headrick
 *
 */
public class Ray 
{
	/**
	 * Ray starting coordinate.
	 */
	public double x0, y0, z0;
	
	/**
	 * Ray slope.
	 */
	public double xd, yd, zd;
	
	/**
	 * Create a new ray that travels from the first point, through the second.
	 * 
	 * @param x0 X coordinate of start point.
	 * @param y0 Y coordinate of start point.
	 * @param z0 Z coordinate of start point.
	 * @param x1 X coordinate of a point on ray.
	 * @param y1 Y coordinate of a point on ray.
	 * @param z1 Z coordinate of a point on ray.
	 * @return Resulting ray.
	 */
	public static Ray rayFromPoints( double x0, double y0, double z0, 
							double x1, double y1, double z1 )
	{
		return new Ray( x0, y0, z0, x1 - x0, y1 - y0, z1 - z0 );
	}
	
	/**
	 * Spawn a ray that goes from (x,y,z) -> (x2,y2,z2)
	 * 
	 * @param x Starting x coordinate.
	 * @param y Starting y coordinate.
	 * @param z Starting z coordinate.
	 * @param xd x slope.
	 * @param yd y slope.
	 * @param zd z slope.
	 */
	public Ray( double x, double y, double z, double xd, double yd, double zd )
	{
		x0 = x; y0 = y; z0 = z;
		
		double a = Math.sqrt((xd * xd) + (yd * yd) + (zd * zd));
		
		this.xd = xd/a;
		this.yd = yd/a;
		this.zd = zd/a;
	}
	
	/**
	 * Move ray.
	 * @param t New T value.
	 */
	public void moveTo( double t )
	{
		this.x0 = getX( t );
		this.y0 = getY( t );
		this.z0 = getZ( t );
	}
	
	/**
	 * Get x value at position on the ray.
	 * @param t T value of the position.
	 * @return Get x value ray at t. 
	 */
	public double getX( double t )
	{
		return x0 + t * xd;
	}
	
	/**
	 * Get y value at position on the ray.
	 * @param t T value of the position.
	 * @return Get y value ray at t. 
	 */
	public double getY( double t )
	{
		return y0 + t * yd;
	}
	
	/**
	 * Get z value at position on the ray.
	 * @param t T value of the position.
	 * @return Get z value ray at t. 
	 */
	public double getZ( double t )
	{
		return z0 + t * zd;
	}
	
	/**
	 * Calculate dot product.
	 * @param other Other Ray to dot with.
	 * @return Dot product of this ray and other.
	 */
	public double dot( Ray other )
	{
		return xd*other.xd + yd*other.yd + zd*other.zd;
	}

	/**
	 * Calculate the closest intersection.
	 * 
	 * @return Find the intersection closest to the ray source.
	 */
	public Intersection findNearestIntersection()
	{
		Intersection result = Intersection.NONE;
		
		double z = Double.MAX_VALUE;
		for( SceneObject so : Scene.instance.objects )
		{
			// find t value of intersection
			Intersection in = so.getIntersection( this );
			
			// if intersection is in front of a previous intersection, update
			if( in != Intersection.NONE )
			{
				if( in.t > 0.1F && in.t < z )
				{
					z = in.t;
					result = in;
				}
			}
		}
		return result;
	}
	
	/**
	 * Test if a ray reaches a point without being blocked.
	 * 
	 * @param x X value to try and reach
	 * @param y Y value to try and reach
	 * @param z Z value to try and reach
	 * @return True if ray is not blocked on path to point.
	 */
	public boolean rayReachesPoint( double x, double y, double z )
	{
		Intersection in = findNearestIntersection();
		
		if( in == Intersection.NONE ) return true;
		
		return in.t > .1 &&
				Math.abs( in.getX() - x0 ) > Math.abs( x - x0 ) && 
				Math.abs( in.getY() - y0 ) > Math.abs( y - y0 ) &&
				Math.abs( in.getZ() - z0 ) > Math.abs( z - z0 );
	}
	
	/**
	 * Get the reflected ray from an intersection.
	 * @param in Intersection point.
	 * @param normal Normal at intersection.
	 * @param source Source of reflected ray.
	 * @return Reflected ray, or null if angle > 90.
	 */
	public Ray getReflectedRay( Intersection in )
	{ 
		Ray normal = in.getNormal();
		double d = 2 * this.dot( normal );
		if( d < 0 ) return null;
		Ray r = new Ray( in.getX(), in.getY(), in.getZ(), 
				normal.xd*d - xd, normal.yd*d - yd, normal.zd*d - zd );
		return r;
	}
	
	/**
	 * Get the transmitted ray.
	 * @param in
	 * @return
	 */
	public Ray getTransmittedRay( Intersection in, float n1, float n2 )
	{
		if( n1 == n2 ) return in.ray;
		Ray normal = new Ray( in.getNormal().x0, in.getNormal().y0, in.getNormal().z0,
				in.getNormal().xd, in.getNormal().yd, in.getNormal().zd );
		
		double cosI = -dot( normal );
		double nit = n1/n2;
		
		if( cosI < 0 )
		{
			nit = 1/nit;
			normal.xd *= -1; normal.yd *= -1; normal.zd *= -1;
		}
		
		double disc = 1+((nit*nit)*((cosI*cosI)-1));
		if( disc < 0 ) return null;
		
		double T = (nit*cosI)-Math.sqrt( disc );
		return new Ray( in.ray.getX(in.t+.00001), 
						in.ray.getY(in.t+.00001), 
						in.ray.getZ(in.t+.00001), 
				(xd * nit) + (normal.xd * T),
				(yd * nit) + (normal.yd * T),
				(zd * nit) + (normal.zd * T));
	}
	
	public String toString()
	{
		return "("+x0+","+y0+","+z0+")->("+xd+","+yd+","+zd+")";
	}
}
